<?php
/* $Id: gmap.module,v 1.51.2.70 2007/04/27 20:27:11 bdragon Exp $ */
// vim:set ft=php:

/**
 * @file
 * GMap Filters is a module to include Google Map in a module
 *
 * GMap filter allows the insertion of a googlemap in a module.  It has
 * a page to creat a macro and then a filter to convet the macro into the
 * html and javascript code required to insert a google map.
 */

define('GMAP_LOAD_METHOD', variable_get('gmap_method','Static'));

// @@@ Remove these and generalize lines. @@@
// These are now stored in the map defaults, but not all the code uses it yet.
define('GMAP_LINECOLOR1', '#00cc00');
define('GMAP_LINECOLOR2', '#ff0000');
define('GMAP_LINECOLOR3', '#0000ff');
// @@@ Remove these and generalize lines. @@@

define('GMAP_CLUSTER', variable_get('gmap_cluster',0));
define('GMAP_WMS', variable_get('gmap_wms',0));

// Minimum is currently V. 2.69 (which implements the new GPolygon stuff)
define('GMAP_API_V', '2.69');

/**
 * Get the defaults for a gmap.
 */
function gmap_defaults() {
  return variable_get('gmap_default', array(
    'width' => '300px',
    'height' => '200px',
    'zoom' => 3,
    'controltype' => 'Small',
    'align' => 'None',
    'latlong' => '40,0',
    'maptype' => 'Map',
    'line_colors' => array('#00cc00', '#ff0000', '#0000ff'),
    'behavior' => array(
      'locpick' => FALSE,
      'nodrag' => FALSE,
      'nokeyboard' => TRUE,
      'nomousezoom' => FALSE,
      'autozoom' => FALSE,
      'dynmarkers' => FALSE, // Load the marker system even if no markers are found
      'fatmarkers' => FALSE, // Pass unrecognized fields with markers, useful for gmap_views.
    ),
  ));
}

/**
 * Set up the HTML header for GMap.
 */
function _gmap_doheader(){
  static $gmap_initialized = FALSE;
  if ($gmap_initialized) {
    return;
  }
  $gmap_path = drupal_get_path('module','gmap');
  drupal_add_css($gmap_path .'/gmap.css');
  drupal_add_js($gmap_path. '/js/gmap.js');
// @@@
drupal_add_js($gmap_path.'/js/poly.js');
  drupal_set_html_head('<script src="http://maps.google.com/maps?file=api&amp;v='.GMAP_API_V.'&amp;key='.variable_get('googlemap_api_key', '').'" type="text/javascript"></script>');
  $gmap_initialized = TRUE;
}

/**
 * Implementation of hook_perm().
 */
function gmap_perm() {
  return array('create macro');
}

/**
 * Returns the html required to insert a map from a gmap associative array.
 *
 * @param $gmap
 * An associative array with the following variables set:
 *
 *  id - the id of the map every map on a page must have a unique id
 *  width - width of the map
 *  height - height of the map
 *  latitude - a string of the latitude f the centre of the map
 *  longitude - a string of the longitude of the centre of the map (the previous string center still works)
 *  zoom - the zoom factor of the google map
 *  align - the alignment of the map 'right', 'left' or 'center'
 *  control - the control shown on the map 'Large', 'Small', or 'None'
 *  tcontrol - whether the type control is on the map or not: 'off' or 'on'
 *  scontrol - whether the scale control is on the map or not: 'off' or 'on'
 *  ocontrol - width and height of the overview control map: 'width,height'
 *  type - 'Map', 'Hybrid' or 'Satellite'
 *  drag - 'yes' or 'no' map is draggable.  Default is 'yes'
 *  markers - an array of associative arrays for the markers.
 *  shapes - an array of associative arrays for the overlays.
 *  track - a file containing a series of points in .plt format to be
 *  inserted into the node.
 *  feeds - an associative array of rss feeds
 *  wmss - an associative array of WMS services
 *
 *  Xmaps must be enabled for circle and polygon to work.
 *
 * @param $javascript
 * Some javascript to insert into function after drawing the map.
 * note that '{id}' will be replaced with the map id.
 *
 * @return
 * A string with the google map ready to be inserted into a node.
 *
 */
function gmap_draw_map($gmap, $javascript='') {
echo "GMap Module: DRAW_MAP CALLED -- This function is scheduled for deletion!<br />";
/*
 * This function will be removed as soon as I can fix all the instances
 * where it was used in the past.
 * The reason for completely removing this function lies in the second parameter:
 * Allowing arbitrary javascript here is somewhat incompatible with the new way
 * of doing things and is terribly inefficient. The recommended method now is to
 * put your code in a js file that extends the javascript GMap API and drupal_add_js
 * it to the page during theming.
 * 
 */

return '';
// The rest of this function is legacy code that still needs picked through.
  global $base_url, $node;
    _gmap_doheader();
    
    if (file_exists('misc/gxmarker.2.js')) {
      $gxmarkerjs=1;
    }
    else {
      $gxmarkerjs=0;
    }

    $defaults = array(
      'autozoom' => FALSE,
      'align' => '',
      'tcontrol' => 'off',
      'scontrol' => 'off',
//    'ocontrol' => 'off',
      'id' => 'map',
      'drag' => 'yes',
    );

      

    // prefer $gmap, then $defaults, then the global defaults..
    $gmap = array_merge(gmap_defaults(),$defaults,$gmap);

    $clusterset=false;

    (!isset($gmap['drag'])) ? $gmap['drag']='yes':NULL;

    if (!isset($gmap['latitude']) && !isset($gmap['center'])&& isset($gmap['latlong'])){
      //backwards compatible with macros created by previous version of this program
      list($gmap['latitude'],$gmap['longitude']) =explode(',',$gmap['latlong']);
    }
    elseif (!isset($gmap['latitude']) && isset($gmap['center'])){
      list($gmap['latitude'],$gmap['longitude']) =explode(',',$gmap['center']);
    }

    
// @@@ FIX THIS!
/*'              var mycontrol=null;
              var mytypecontrol=null;
              var '.$gmap['id'].'=null;
              var gxmarkerjs='.$gxmarkerjs.';
              var gmapMarkerLocation="/'.variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'";
              var markerlink='.variable_get('gmap_on_click',0).';
              var bounds = new GLatLngBounds();
';
    if (isset($gmap['wmss'])) {
      $outtext.= '
                 // WMS layers ';
      $outtext.= '
                 '.$gmap['id'].'.getMapTypes().length = 0;';
      $ic=0;
      foreach ($gmap['wmss'] as $item) {
        $map_name = eregi_replace("[^a-z0-9_-]", "_", $item['name']);
        (!isset($item['minresolution'])) ? $item['minresolution']='1': NULL;
        (!isset($item['maxresolution'])) ? $item['maxresolution']='17': NULL;
        (!isset($item['format'])) ? $item['format']='image/gif': NULL;
        (!isset($item['merczoomlevel'])) ? $item['merczoomlevel']='5': NULL;

        $outtext.= '
                 var ccol_'.$map_name.' = new GCopyrightCollection("'.$item['name'].'");
        ';
        if(isset($item['copyrights'])){
          foreach ($item['copyrights'] as $copyright) {
            (!isset($copyright['bounds'])) ? $copyright['bounds']='-190,-90,180,90': NULL;
            (!isset($copyright['minzoom'])) ? $copyright['minzoom']='1': NULL;
            (!isset($copyright['text'])) ? $copyright['text']='': NULL;
            $coords = explode(",", $copyright['bounds']);
            $outtext.= '
                 var sw_'.$ic.' = new GLatLng('.$coords[0].','.$coords[1].',false);
                 var ne_'.$ic.' = new GLatLng('.$coords[2].','.$coords[3].',false);
                 var llb_'.$ic.' = new GLatLngBounds(sw_'.$ic.',ne_'.$ic.');
                 var cr_'.$ic.' = new GCopyright(1,llb_'.$ic.','.$copyright['minzoom'].',"'.$copyright['text'].'");
                 ccol_'.$map_name.'.addCopyright(cr_'.$ic.');
            ';
            $ic++;
          }
        }
        $outtext.= '
                 var t_'.$map_name.'= new GTileLayer(ccol_'.$map_name.','.$item['minresolution'].','.$item['maxresolution'].');
                 t_'.$map_name.'.myMercZoomLevel='.$item['merczoomlevel'].';
                 t_'.$map_name.'.myBaseURL=\''.$item['url'].'\';
                 t_'.$map_name.'.myLayers=\''.$item['layers'].'\';
                 t_'.$map_name.'.myFormat=\''.$item['format'].'\';
                 t_'.$map_name.'.myMapname=\''.$gmap['id'].'\';
                 t_'.$map_name.'.getTileUrl=CustomGetTileUrl;
                 ';
          if(isset($item['opacity'])){
            $outtext.= "t_".$map_name.".myOpacity=".$item['opacity'].";\n                 \n";
            $outtext.= "t_".$map_name.".getOpacity=customOpacity;\n                 \n";
          }
          if(isset($item['overlaywith'])){
            switch (strtolower($item['overlaywith'])) {
              case 'map':
                      $outtext.= "var l_".$map_name."1=[G_NORMAL_MAP.getTileLayers()[0]]; \n                 ";
                break;
              case 'hybrid':
                      //We overlay with roads only, not with the satellite imagery
                      $outtext.= "var l_".$map_name."1=[G_HYBRID_MAP.getTileLayers()[1]]; \n                 ";
                break;
              case 'satellite':
                      $outtext.= "var l_".$map_name."1=[G_SATELLITE_MAP.getTileLayers()[0]]; \n                 ";
                break;
              default:
                      $outtext.= "var l_".$map_name."1=[]; \n                 ";
            }
          }
        $outtext.= 'var l_'.$map_name.'2=[t_'.$map_name.'];';

            switch (strtolower($item['overlaywith'])) {
              case 'hybrid':
              $outtext.= '
                 var l_'.$map_name.'=l_'.$map_name.'2.concat(l_'.$map_name.'1);';
                break;
              default:
              $outtext.= '
                 var l_'.$map_name.'=l_'.$map_name.'1.concat(l_'.$map_name.'2);';
            }

        $outtext.= '                 
                 var m_'.$map_name.' = new GMapType(l_'.$map_name.', G_SATELLITE_MAP.getProjection(), "'.$item['name'].'", G_SATELLITE_MAP); 
                 '.$gmap['id'].'.addMapType(m_'.$map_name.');
                 ';
      }
  
    }

    if (strtolower($gmap['tcontrol'])=='on' || strtolower($gmap['tcontrol'])=='yes') {
      $outtext .= $gmap['id'].'.addControl(mytypecontrol=new GMapTypeControl());
                ';
    }
    if (strtolower($gmap['scontrol'])=='on' || strtolower($gmap['scontrol'])=='yes') {
      $outtext .= $gmap['id'].'.addControl(myscalecontrol=new GScaleControl());
                ';
    }
    if (isset($gmap['ocontrol']) && strlen($gmap['ocontrol']) > 0) {
      $osize = explode(",", $gmap['ocontrol']);
      if (intval($osize[0]) > 0 and intval($osize[1]) > 0) {
        $outtext .= $gmap['id'].'.addControl(myovcontrol=new GOverviewMapControl(new GSize('.$osize[0].','.$osize[1].')));
                ';
      }
    }

    if (isset($gmap['tracks'])) {
      foreach ($gmap['tracks'] as $value) {
        $tt=$value;
        $tt['type']='line';
        $tt['points']=array();
        if($trackFH = fopen($tt['filename'], "r")) {
          while(!feof($trackFH)) {
            $line = fgets($trackFH, 4096);
            $line_exploded = explode(",", $line);
            if(count($line_exploded) == 7) {
              $tt['points'][] = array('latitude' => trim($line_exploded[0]),
                                      'longitude' => trim($line_exploded[1]));
            }
          }
          fclose($trackFH);
        }

        $gmap['shape'][]=$tt;
        
      }
    }

    if (isset($gmap['markers'])) {
      if (GMAP_CLUSTER) {
        $outtext .="\n var clusterer = new Clusterer(".$gmap['id'].");\n";
        $markerfile = $base_url.'/'.variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.variable_get('gmap_cluster_marker', 'cluster').'.png';
        if (file_exists($markerfile)) {
          $outtext .=" clusterIcon = createIcon('$markerfile') \n";
          $outtext .=" clusterer.SetIcon(clusterIcon);\n";
        }
        $outtext .=" clusterer.SetMaxVisibleMarkers(".variable_get('gmap_cluster_max_markers', 150).");\n";
        $outtext .=" clusterer.SetMinMarkersPerCluster(".variable_get('gmap_cluster_min_per_cluster', 5).");\n";
        $outtext .=" clusterer.SetMaxLinesPerInfoBox(".variable_get('gmap_cluster_max_lines', 10).");\n\n";
        $clusterset=true;
      }
      $lastmarker='';
      foreach ($gmap['markers'] as $item) {
        $mymarker='';
        $mytext='';
        if (isset($item['markername'])){
          if ($item['markername']==$lastmarker) {
            $i++;
          }
          else {
            $lastmarker=$item['markername'];
            $i=1;
          }
          if (file_exists(variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].$i.'.png')) {
            $mymarker=$base_url.'/'.variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].$i.'.png';
          }
          elseif (file_exists(variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].'.png')) {
            $mymarker=$base_url.'/'.variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].'.png';
          }
        }
        if (isset($item['text']) && strlen(trim($item['text']))>0) {
          $mytext=$item['text'];
        }
        elseif (isset($item['label']) && strlen(trim($item['label']))>0) {
          $mytext=$item['label'];
        }
        $divstyle='';
        if (isset($item['winwidth']) && trim($item['winwidth']) != '') {
          $divstyle='width: '.$item['winwidth'].'; ';
        }
        if (strlen($divstyle) >0 && strlen(trim($mytext))>0) {
          $mytext = '<div class="gmap-popup" style="'.$divstyle.'">'.$mytext.'</div>';
        }
        elseif (strlen(trim($mytext))>0){
          //special line to insert nodeid into text.
          //$mytext=str_replace('##NODE##', $node->nid, $mytext);
          $mytext = '<div class="gmap-popup">'.$mytext.'</div>';
        }
        else {
          $mytext='';
        }
        if (isset($item['tooltip'])) {
          $tooltip=check_plain($item['tooltip']);
        }
        else {
          $tooltip='';
        }
        if (isset($item['link'])) {
          $link=$item['link'];
        }
        else {
          $link='';
        }
        if (GMAP_CLUSTER) {
          $outtext .='clusterer.AddMarker(createGMarker(new GLatLng('.$item['latitude'].','.$item['longitude'].'),\''.$mytext.'\',\''.$mymarker."','$tooltip','$link'),'".l($tooltip,$base_url.$link)."');\n";
          $clusterset=true;
        }

    if (isset($gmap['shape']) && !variable_get('gmap_xmaps',0)) {
      //if xmaps is not enabled then just show the lines using straight google maps.
      foreach ($gmap['shape'] as $value) {
        $style ='';
        if (trim($value['type'])=='line') {
          if (isset($value['color'])) {
            $style .=",'".$value['color']."'";
            if (isset($value['width'])) {
              $style .=','.$value['width'];
              if (isset($value['opacity'])) {
                $style .=','.$value['opacity'];
              }
            }
          }
//          $linetxt ='new GLatLng('.implode('), new GLatLng(',$value['latitude'].','.$value['longitude']).')';
          $linetxt='';
          foreach ($value['points'] as $lvalue) {
            if (strlen($linetxt)>0) {
              $linetxt .= ', ';
            }
            $linetxt .="new GLatLng(".$lvalue['latitude'].','.$lvalue['longitude'].")";
          }
          $outtext .="\n".$gmap['id'].".addOverlay(new GPolyline([$linetxt] $style));";
        }
      }
    }

    if (isset($gmap['feeds'])) {
//      if (GMAP_CLUSTER && !$clusterset) {
//        $outtext .="\n var clusterer = new Clusterer(".$gmap['id'].");\n";
 //     }
      foreach ($gmap['feeds'] as $item) {
        $mymarker='';
        if (isset($item['markername'])){
          if (file_exists(variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].'.png')) {
            $mymarker=$base_url.'/'.variable_get('gmap_markerfiles',drupal_get_path('module', 'gmap') .'/markers').'/'.$item['markername'].'.png';
          }
        }
        if (isset($item['url'])) {
          $outtext.= "parseGeoRSS(".$gmap['id'].",'".$item['url']."','$mymarker');\n";
        }
      }
    }

    if (strlen($javascript)>0) {
      $javascript=str_replace('{id}',$gmap['id'],$javascript);
      $outtext .=$javascript;
    }
    $outtext .="\n } \n   //]]>
               </script>";
    return $outtext;


 */
}

/**
 * Cleans the gmap variables to prevent javascript interjection.
 *
 * @param $gmap
 * A Gmap variable
 *
 * @return
 * A GMap variable with any dangerous text removed.
 *
 * This does not really do much of anything right now.
 */
function gmap_sanitize(&$gmap){
  //sanitizes the gmap variables to reduce the possibility of javascript inserts
  foreach ($gmap as $key=>$value) {
    if ($key=='id') {
      $out = array();
      preg_match('([a-zA-Z0-9_-]*)', $value, $out);
      if (strlen($out[0])==0)  {
        $out[0]='map';
      }
      $gmap[$key]=$out[0];
    }
    else {
      $gmap[$key]=str_replace(';','',$value);
    }
  }
}

/**
 *
 * Returns a variable based on .
 *
 * @param $instring
 * A string with the settings of gmap insertion in the format var=setting|var2=setting2
 *  The possible variables are
 *  id - the id of the map every map on a page must have a unique id
 *  width - width of the map
 *  height - height of the map
 *  center - a string of the longitude and latitude of the centre of the map
 *  zoom - the zoom factor of the google map
 *  align - the alignment of the map 'right', 'left' or 'center'
 *  control - the control shown on the map 'Large', 'Small', or 'None'
 *  type - 'Map', 'Hybrid' or 'Satellite'
 *  points/markers - a string of points to mark on the map with + between
 *          each point
 *  line - the line is defined by a set of points separated by a +
 *  track - Draws a line based on the points in the .plt file
 *  The following shape types require XMaps:
 *  circle - a circle based on a center point and a radius in km separated
 *          by a + and optionally can include the number of sizes.
 *  rpolygon - a regular polygon is defined by the center point and a point
 *          on the permiter separated by a +
 *  polygon - a polygon is defined by a set of points
 *
 *  Each of the shapes types can optionally have charecteristics of colour,
 *  width, opacity, pattern, text, fill colour, fill opacity.  Pattern, text
 *  and fill are all only used by xmaps.
 *      color - hexadecimal for the colour include the '#'
 *
 *
 * @return
 * A string with the google map ready to be inserted into a node.
 *
 */
function gmap_parse_macro($instring,$ver=2) {

  // Remove leading and trailing tags
  if (substr(trim($instring),-1)==']') {
    $instring=substr(trim($instring), 0, -1);
  }
  if (substr($instring, 0,5)=='[gmap') {
    $instring=substr($instring, 6);
  }

  // Chop the macro into an array
  $temp = explode('|',$instring);
  $m = array();
  foreach ($temp as $row) {
    if (strlen(trim($row)) > 0) {
      $r = explode('=',$row);
      // Things that can appear multiple times
      if (in_array(trim($r[0]),array('points','markers','feed'))) {
        $m[trim($r[0])][] = trim($r[1]);
      }
      else {
        $m[trim($r[0])] = trim($r[1]);
      }
    }
  }

  // Synonyms
  if ($m['type']) {
    $m['maptype'] = $m['type'];
    unset($m['type']);
  }

  $m['lines'] = array();
  $m['delme'] = array(); // List of keys to delete.

  // Look for keys needing handling
  reset($m);
  while (list($k,$v) = each($m)) {

    // Put all lines in the line array.
    if((strpos($k,'line')===0) && $k!='lines') {
      $m['lines'][intval(substr($k,4))-1] = _gmap_str2points($v);
      $m['delme'][] = $k;
    }
  }
  // Clean up unneeded elements.
  foreach($m['delme'] as $k => $v) {
    unset($m[$v]);
  }
  unset($m['delme']);

  if (is_array($m['feed'])) {
    foreach ($m['feed'] as $k => $v) {
      $temp = explode('::',$v);
      // Normalize url
      if (substr($temp[1],0,1) == '/') {
        $temp[1] = substr($temp[1],1);
      }
      $temp[1] = url($temp[1]);
      $m['feed'][$k] = array(
        'markername' => $temp[0],
        'url' => $temp[1],
      );
    }
  }

  // Merge points and markers
  if(!is_array($m['points'])) $m['points'] = array();
  if(!is_array($m['markers'])) $m['markers'] = array();
  $m['markers-temp'] = array_merge($m['points'],$m['markers']);
  unset($m['points']);
  unset($m['markers']);

  // Version 1 -> 2 conversion
  if ($ver==1) {
    // Zoom is flipped
    if ($m['zoom']) {
      $m['zoom'] = 18 - $m['zoom'];
      if ($m['zoom'] < 1) {
        $m['zoom'] = 1;
      }
    }
  }

  // Center -> latitude and longitude
  if ($m['center']) {
    list($m['latitude'],$m['longitude']) = explode(',',$m['center']);
    unset($m['center']);
  }

  // Behavior
  if ($m['behaviour']) {
    $m['behavior'] = $m['behaviour'];
    unset($m['behaviour']);
  }
  if ($m['behavior']) {
    $m['behavior-temp'] = explode(' ',$m['behavior']);
    $m['behavior'] = array();
    foreach ($m['behavior-temp'] as $v) {
      $m['behavior'][substr($v,1)] = (substr($v,0,1)=='+') ? TRUE : FALSE;
    }
    unset($m['behavior-temp']);
  }

  // Markers fixup
  foreach ($m['markers-temp'] as $t) {
    unset($markername);
    // Named?
    if(strpos($t,'::')) { // Single : gets handled below.
      list($markername,$t) = explode('::',$t,2);
    }
    // Break down into points
    $points = explode('+',$t);
    $offset = 0;
    foreach ($points as $point) {
      $marker = array();
      $offset++;
      // Labelled?
      // @@@ Gmap allows both a tooltip and a popup, how to represent?
      if (strpos($point,':')) {
        list($point,$marker['text']) = explode(':',$point,2);
        $marker['text'] = theme('gmap_marker_popup',$marker['text']);
      }
      list($marker['latitude'],$marker['longitude']) = explode(',',$point,2);
      // Named markers get an offset too.
      if (isset($markername)) {
        $marker['markername'] = $markername;
        $marker['offset'] = $offset;
      }
      $m['markers'][] = $marker;
    }
  }
  unset($m['markers-temp']);


/*      case 'track':
        $tt['color']=GMAP_LINECOLOR1;
        if (strpos($t[1],':')) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'])=explode('/',$configs);
        }
        $tt['filename'] =$t[1];
        $gmap['tracks'][] = $tt;
        break;
        
      case 'feed':
        if (strpos($t[1],'::')) { // note: we don't care about case starting with ':'
          list($markername,$t[1])=explode('::',$t[1],2);
        }
        $tt['url'] =$t[1];
        if (isset($markername)){
          $tt['markername']=$markername;
        }
        $gmap['feeds'][] = $tt;
        break;

      case 'wmss':
        unset($ttt);
        $ttt = explode('+',$t[1]);
        for ($i =0; $i<count($ttt); $i++) {
          unset($tt);
          list($tt['name'],$tt['url'],$tt['format'],$tt['layers'],$tt['minresolution'],$tt['maxresolution'],$tt['copyrights'],$tt['overlaywith'],$tt['merczoomlevel'],$tt['opacity'])=explode('::',$ttt[$i]);
          $crr = explode('/',$tt['copyrights']);
          unset($tt['copyrights']);
          for ($k =0; $k<count($crr); $k++) {
            unset($cr);
            list($cr['minzoom'],$cr['bounds'],$cr['text'])=explode(':',$crr[$k]);
            $tt['copyrights'][]=$cr;
          }
          $gmap['wmss'][]=$tt;
        }
        break;

      case 'line1':
        $tt['color']=GMAP_LINECOLOR1;
      case 'line2':
        if (empty($tt['color'])){
          $tt['color']=GMAP_LINECOLOR2;
        }
      case 'line3':
        if (empty($tt['color'])){
          $tt['color']=GMAP_LINECOLOR3;
        }
      case 'line':
        if (strpos($t[1],':')) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'])=explode('/',$configs);
        }
        $tt['points'] = explode('+',$t[1]);
        foreach ($tt['points'] as $key=>$value) {
          $tt['points'][$key]=gmap_dopoint($value,$ver);
        }

        $tt['type'] = 'line';
        $gmap['shape'][] = $tt;
        break;

      case 'circle':
        if (strpos($t[1],':')) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'],$tt['fillcolor'],$tt['fillopacity'])=explode('/',$configs);
        }
        list($tt['center'],$tt['radius'],$tt['sides'])= explode('+',$t[1]);
        $tt['type']='circle';
        $gmap['shape'][] = $tt;
        break;

      case 'rpolygon':
        if (strpos($t[1],':')) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'],$tt['fillcolor'],$tt['fillopacity'])=explode('/',$configs);
        }
        list($tt['center'],$tt['point'],$tt['sides']) = explode('+',$t[1]);
        $tt['type']='rpolygon';
        $gmap['shape'][] = $tt;
        break;

      case 'circle':
        if (strpos($t[1],':')) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'],$tt['fillcolor'],$tt['fillopacity'])=explode('/',$configs);
        }
        list($tt['center'],$tt['radius'],$tt['sides'])= explode('+',$t[1]);
        $tt['type']='circle';
        $gmap['shape'][] = $tt;
        break;

      case 'polygon':
        $tt['color']=GMAP_LINECOLOR1;
        if (strpos(':',$t[1])) { // note: we don't care about case starting with ':'
          list($configs,$t[1])=explode(':',$t[1],2);
          list($tt['color'],$tt['width'],$tt['opacity'],$tt['pattern'],$tt['text'],$tt['fillcolor'],$tt['fillopacity'])=explode('/',$configs);
        }
        $tt['points'] = explode('+',$t[1]);
        $tt['type']='polygon';
        $gmap['shape'][] = $tt;
        break;

      case 'control':
        $gmap['controltype'] = trim($t[1]);
        break;

      case 'type':
        $gmap['maptype'] = trim($t[1]);
        break;

      default:
        $gmap[trim($t[0])] = trim($t[1]);
      }
    $j++;
  }
  gmap_sanitize($gmap);
  return $gmap;
*/

  return $m;
}

/**
 * Parse a set of points in string format
 */
function _gmap_str2points($str) {
  // Explode along + axis
  $arr = explode(' + ',$str);
  // Explode along , axis
  $points = array();
  foreach($arr as $pt) {
    list($lat,$lon) = explode(',',$pt);
    $points[] = array(
      'latitude' => trim($lat),
      'longitude' => trim($lon),
    );
  }
  return $points;
}

/**
 * Theme a marker popup.
 * This will get called for markers embedded in macros.
 */
function theme_gmap_marker_popup($label) {
  return $label;
}

/**
 *
 * Creates a map that can be interactively used to fill a form with a
 * location (latitude, longitude and zoom level)
 *
 * @param $map
 * Either a macro to use as the base map for setting a location, or an already set map associative array.
 * @param $form
 * A formset associative array. Cannot be more than one deep.
 * @param $fields
 * An associative array for the field names.  'latitude', 'longitude'=>name of respective array, 'address' is optional.
 * @return
 * A string with the google map code to be inserted onto the page.
 *
 */
function gmap_set_location($map,&$form,$fields) {
  static $ctr = 0;
  $ctr++;
  if (!is_array($map)){
    $map = gmap_parse_macro($map);
  }
  $id = 'loc'.$ctr;
  $map['id'] = $id;

  // This is a locpick map.
  $map['behavior']['locpick'] = TRUE;

  $element = array(
    '#type' => 'gmap',
    '#settings' => $map,
  );

  $form[$fields['latitude']]['#map']=$id;
  gmap_widget_setup($form[$fields['latitude']], 'locpick_latitude');

  $form[$fields['longitude']]['#map']=$id;
  gmap_widget_setup($form[$fields['longitude']], 'locpick_longitude');

  if (isset($fields['address'])) {
    $form[$fields['address']]['#map'] = $id;
    gmap_widget_setup($form[$fields['address']], 'locpick_address');
  }
  return theme('gmap',$element);
}

function _gmap_prepare($intext) {
  $out = FALSE;
  $matches = array();
  preg_match_all('/\[gmap([^\[\]]+ )* \] /x', $intext, $matches);
  $i = 0;

  while (isset($matches[1][$i])) {
    $out[0][$i] = $matches[0][$i];
    if ($matches[1][$i][0]=='1'){
      $ver=1;
      $matches[1][$i]=substr($matches[0][$i],1);
    }
    else {
      $ver=2;
    }
    $map = array('#settings'=>gmap_parse_macro($matches[1][$i],$ver));
    $out[1][$i] = theme('gmap',$map);
    $i++;
  } // endwhile process macro
  return $out;
}

function gmap_todim($instring) {
  if (strpos($instring,'%')) {
    return intval($instring).'%';
  }
  else {
    return intval($instring).'px';
  }
}

function gmap_dopoint($point,$ver) {
  if ($ver==1) {
    $lonlat=explode(',',$point,2);
    return array('latitude'=>$lonlat[1],'longitude'=>$lonlat[0]);
  }
  else {
    $lonlat=explode(',',$point,2);
    return array('latitude'=>$lonlat[0],'longitude'=>$lonlat[1]);
  }
}

function gmap_filter($op, $delta = 0, $format = -1, $text = '') {
  switch ($op) {
    case 'list':
      return (array(0 => t('GMap filter')));

    case 'name':
      return t('Google map filter');

    case 'description':
      return t('converts a google map  macro into the html required for inserting a google map.');

    case 'process':
      $gmaps=_gmap_prepare($text);   //returns an array of $tables[0] = table macro $table[1]= table html
      if ($gmaps) {                    // there are table macros in this node
        return str_replace($gmaps[0], $gmaps[1], $text);
      }
      else {
        return $text;
      }

    case 'prepare':
      return $text;

    case 'no cache':
      return TRUE;
      return (variable_get('gmap_method', 'Static')=='Dynamic');
  }
}

function gmap_filter_tips($delta, $format, $long = false) {
  if(user_access('create macro')) { // only display macro if user can create one
    return t('Insert Google Map macro.').'<a href="'.url('map/macro').'" target="_blank" >'.t('Create a macro').'</a>';
  }
  else {
    return t('Insert Google Map macro.');
  }
}

function gmap_menu($may_cache) {
  $items=array();

  if ($may_cache) {
/*    $items[] = array(
      'path' => 'map',
      'type' => MENU_ITEM_GROUPING,
      'title' => t('google maps'),
      'access' => user_access('create macro')||user_access('show user map')||user_access('show node map'),
    );  */
    $items[] = array(
      'path' => 'admin/settings/gmap',
      'title' => t('GMap'),
      'description' => t('Configure GMap settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'gmap_admin_settings',
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'map/query',
      'type' => MENU_CALLBACK,
      'access' => TRUE, //@@@
      'callback' => 'gmap_json_query',
    );

  }
/*  if (!$may_cache && (strlen(variable_get('googlemap_api_key', '')) > 0)) {
    if (variable_get('gmap_method', 'Static')=='Static') {
      _gmap_doheader();
    }
  }*/
  return $items;
}

/**
 * JSON request interface.
 */
function gmap_json_query() {
  // Marker sequence
  if (arg(2)=='markerseq') {
    $markerdir = drupal_get_path('module', 'gmap').'/markers';
    $marker_images = file_scan_directory($markerdir, '.*\.png$',array('shadow.png','.','..'),0,true, 'filename');
    foreach($marker_images as $k => $v) {
      $marker_images[$k]->filename = base_path() . $marker_images[$k]->filename;
    }
    echo drupal_to_js($marker_images);
    exit();
  }
  if (arg(2)=='markers') {
    drupal_set_header('Content-Type: text/javascript');
    echo drupal_to_js(array(
      'path' => base_path() . drupal_get_path('module', 'gmap').'/markers',
      'markers' => gmap_get_icondata(TRUE),
    ));
    exit();
  }
  if (arg(2)=='marker') {
    $name = arg(3);
  }
}

function gmap_admin_settings() {
  require_once(drupal_get_path('module','gmap') .'/gmap_settings_ui.inc');
  return _gmap_admin_settings();
}

/**
 * Implementation of hook_elements().
 */
function gmap_elements() {
  return array(
    'gmap' => array(
      '#input' => FALSE, // This isn't a *form* input!!
      '#settings' => array_merge(gmap_defaults(),array(
        'points' => array(),
        'pointsOverlays' => array(),
        'lines' => array(),
      )),
      '#process' => array('expand_gmap' => array()),
    ),
    'gmap_macrotext' => array(
      '#input' => TRUE,
      '#cols' => 60,
      '#rows' => 5,
      '#process' => array(
        'process_gmap_control' => array('textarea','macrotext')
      ),
    ),
    'gmap_overlay_edit' => array('#input' => FALSE, '#process' => array('process_gmap_overlay_edit' => array())),
    'gmap_address' => array('#input' => FALSE, '#process' => array('process_gmap_address' => array())),
    'gmap_align' => array('#input' => TRUE, '#process' => array('process_gmap_align' => array())),
    'gmap_latitude' => array('#input' => TRUE, '#process' => array('process_gmap_control' => array('textfield','latitude','gmap_coord'))),
    'gmap_longitude' => array('#input' => TRUE, '#process' => array('process_gmap_control' => array('textfield','longitude','gmap_coord'))),
    'gmap_latlon' => array('#input' => TRUE, '#process' => array('process_gmap_control' => array('textfield','latlon','gmap_coord'))),
    'gmap_markerchooser' => array('#input' => TRUE, '#process' => array('process_gmap_markerchooser' => array())),
  );
}

/**
 * Gmap element process hook
 */
function expand_gmap($element) {
  $mapid = 'map';
  if ($element['#map']) {
    $mapid = $element['#map'];
  }
  else {
    $element['#map'] = $mapid;
  }
  if (!$element['#settings']) {
    $element['#settings'] = array();
  }
  $element['#settings'] = array_merge(gmap_defaults(),array(
    'id' => $mapid,
    'points' => array(),
    'pointsOverlays' => array(),
    'lines' => array(),
  ),$element['#settings']);
  gmap_widget_setup($element, 'gmap');
  return $element;
}

/**
 * Generic gmap control processor
 */
function process_gmap_control($element,$edit,$fieldtype,$control,$theme='') {
  $element['#type'] = $fieldtype;
  gmap_widget_setup($element, $control);
  if (!empty($theme)) {
    $element['#theme'] = $theme;
  }
  else {
    $element['#theme'] = 'gmap_'.$control;
  }
  return $element;
}

function process_gmap_overlay_edit($element) {
  // Conver the root element into a fieldset.
  $element['#type'] = 'fieldset';
  if (!$element['#title']) {
    $element['#title'] = t('Overlay editor');
  }
  $element['#tree'] = TRUE;

  $element['mapclicktype'] = array(
    '#type' => 'select',
    '#title' => t('Click map'),
    '#map' => $element['#map'],
    '#options' => array(
      'Points' => t('Points'),
      'Lines' => t('Lines'),
      'Circles' => t('Circles'),
    ),
  );
  gmap_widget_setup($element['mapclicktype'],'overlayedit_mapclicktype');
  $element['markerclicktype'] = array(
    '#type' => 'select',
    '#title' => t('Click marker'),
    '#map' => $element['#map'],
    '#options' => array(
      'Remove' => t('Remove'),
      'Edit Info' => t('Edit Info'),
    ),
  );
  gmap_widget_setup($element['markerclicktype'],'overlayedit_markerclicktype');
  
  $element['marker'] = array(
    '#type' => 'select',
    '#map' => $element['#map'],
    '#options' => gmap_get_marker_titles(),
    '#title' => t('Marker'),
    '#theme' => 'gmap_overlay_edit',
  );
  gmap_widget_setup($element['marker'], 'overlayedit');

  $element['linesettings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Line/Polygon settings'),
    '#tree' => TRUE,
  );

  $element['linesettings']['scol'] = array(
    '#type' => 'textfield',
    '#map' => $element['#map'],
    '#size' => 6,
    '#maxlength' => 6,
    '#field_prefix' => '#',
    '#title' => t('Stroke color'),
    '#default_value' => '000000',
  );
  gmap_widget_setup($element['linesettings']['scol'], 'overlayedit_scol');

  $element['linesettings']['sweight'] = array(
    '#type' => 'textfield',
    '#map' => $element['#map'],
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => t('px'),
    '#title' => t('Stroke weight'),
    '#default_value' => 1,
  );
  gmap_widget_setup($element['linesettings']['sweight'], 'overlayedit_sweight');

  $element['linesettings']['sopac'] = array(
    '#type' => 'textfield',
    '#map' => $element['#map'],
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => '%',
    '#title' => t('Stroke opacity'),
    '#default_value' => '100',
  );
  gmap_widget_setup($element['linesettings']['sopac'], 'overlayedit_sopac');

  $element['linesettings']['fcolor'] = array(
    '#type' => 'textfield',
    '#map' => $element['#map'],
    '#size' => 6,
    '#maxlength' => 6,
    '#field_prefix' => '#',
    '#title' => t('Fill color'),
    '#default_value' => '00FF00',
  );
  gmap_widget_setup($element['linesettings']['fcolor'], 'overlayedit_fcolor');

  $element['linesettings']['fopac'] = array(
    '#type' => 'textfield',
    '#map' => $element['#map'],
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => '%',
    '#title' => t('Fill opacity'),
    '#default_value' => '60',
  );
  gmap_widget_setup($element['linesettings']['fopac'], 'overlayedit_fopac');

  return $element;
}

function process_gmap_align($element) {
  $element['#type'] = 'select';
  gmap_widget_setup($element, 'align');
  $element['#options'] = drupal_map_assoc(array('None', 'Right', 'Left', 'Center'));
  $element['#theme'] = 'gmap_align';
  return $element;
}

function process_gmap_address($element) {
  $element['#type'] = 'textfield';
  gmap_widget_setup($element, 'address');
  $element['#theme'] = 'gmap_address';
  return $element;
}

function process_gmap_markerchooser($element) {
  $element['#type'] = 'select';
  $element['#options'] = gmap_get_marker_titles();
  return $element;
}

function theme_gmap_overlay_edit($element) {
  drupal_add_js(drupal_get_path('module','gmap') .'/js/gmap.js');
  drupal_add_js(drupal_get_path('module','gmap') .'/js/overlay_edit.js');
  return theme('select',$element);
}

/**
 * Perform some normalization on the map object
 * to prevent errors.
 */
function gmap_map_cleanup(&$map) {
  // Google is picky about this one.
  $map['zoom'] = (int)$map['zoom'];
  // JSON query handler
  $map['querypath'] = url('map/query',NULL,NULL,TRUE);
  // @@@ This really needs to be per-map.
  $map['markermode'] = variable_get('gmap_on_click',0);
  // Normalize latitude / longitude
  if ($map['latlong']) {
    $map['latlon'] = $map['latlong'];
    unset($map['latlong']);
  }
  if (isset($map['latlon']) && (!isset($map['latitude']) || !isset($map['longitude']))) {
    list($map['latitude'],$map['longitude']) = explode(',',$map['latlon']);
  }
  unset($map['latlon']);
}

function theme_gmap_coord($element) {
  //drupal_add_js
  return theme('textfield',$element);
}

function theme_gmap_macrotext($element) {
  drupal_add_js(drupal_get_path('module','gmap') .'/js/macro.js');
  return theme('textarea',$element);
}

function theme_gmap_address($element) {
  drupal_add_js(drupal_get_path('module','gmap') .'/js/address.js');
  return theme('textfield',$element);
}

function theme_gmap_align($element) {
  drupal_add_js(drupal_get_path('module','gmap') .'/js/align.js');
  return theme('select',$element);
}

/**
 * Gmap element theme hook
 */
function theme_gmap($element) {
  _gmap_doheader();
  $path = drupal_get_path('module','gmap') .'/js/';

  // If the theme function is called directly, the map is not properly set up yet.
  if(!$element['#id']) {
    gmap_widget_setup($element, 'gmap');
  }
  // Possible if this gmap was created manually.
  if (!$element['#settings']) {
    $element['#settings'] = array();
  }
  $map = array_merge(gmap_defaults(),$element['#settings']);
  gmap_map_cleanup($map);

  switch (strtolower($map['align'])) {
    case 'left':
      $element['#attributes']['class'] += ' gmap-left';
      break;
    case 'right':
      $element['#attributes']['class'] += ' gmap-right';
      break;
    case 'center':
    case 'centre':
      $element['#attributes']['class'] += ' gmap-center';
  }

  $style = array();
  $style[] = 'width: '.$map['width'];
  $style[] = 'height: '.$map['height'];

  $element['#attributes']['class'] = trim($element['#attributes']['class'] .'gmap gmap-map gmap-'.$map['id'].'-gmap');
  $o = '<div id="'.$element['#id'].'" style="'.implode('; ',$style).';"'.drupal_attributes($element['#attributes']).'></div>';

  // Activate markers if needed
  if ($map['behavior']['dynmarkers'] || !empty($map['markers'])) {
    drupal_add_js($path .'icon.js');
    drupal_add_js($path .variable_get('gmap_mm_type','gmap_marker.js'));
  }

  if ($map['behavior']['locpick']) {
    drupal_add_js($path . 'locpick.js');
  }

  if(variable_get('gmap_load_zoom_plugin',TRUE) && !$map['behavior']['nomousezoom']) {
    drupal_add_js(drupal_get_path('module','gmap').'/thirdparty/mousewheel.js');
  }

  if ($map['markers'] || $map['lines']) {
    drupal_add_js($path . 'markerloader_static.js');
  }

  if (is_array($map['feed'])) {
    drupal_add_js($path . 'markerloader_georss.js');
  }

  // Inline settings extend.
  $o .= '<script type="text/javascript">Drupal.extend({ settings: '.drupal_to_js(array('gmap' => array($element['#map'] => $map)))." });</script>\n"; 
  return $o;
}

/**
 * Set up widget.
 * This function will change a form element's ID so it is found
 * by the GMap handlers system.
 */
function gmap_widget_setup(&$element, $type) {
  if (!isset($element['#map'])) {
    // Hmm, missing #map. Try to figure it out.
    if (isset($element['#settings']['id'])) {
      $element['#map'] = $element['#settings']['id'];
    }
  }
  $element['#attributes']['class'] = trim(implode(' ',array(
    $element['#attributes']['class'],
    'gmap-control',
    'gmap-'.$type,
  )));
  $element['#id'] = gmap_get_id($element['#map'],$type);
}

/**
 * Get a CSS id for a map and type.
 * Since CSS ids have to be unique, GMap related IDs are assigned by
 * this function.
 */
function gmap_get_id($map,$type) {
  static $serial = array();
  if (!isset($serial[$map])) {
    $serial[$map] = array();
  }
  if (!isset($serial[$map][$type])) {
    $serial[$map][$type] = -1;
  }
  $serial[$map][$type]++;
  return 'gmap-'.$map.'-'.$type.$serial[$map][$type];
}

/**
 * Get the list of marker titles.
 */
function gmap_get_marker_titles($reset=FALSE){
  static $titles;
  if (is_array($titles) && !$reset) {
    return $titles;
  }
  
  $titles = cache_get('gmap_marker_titles');
  if ($titles) {
    $titles = unserialize($titles->data);
  }

  if ($reset || !$titles) {
    require_once(drupal_get_path('module','gmap') .'/gmap_markerinfo.inc');
    $titles = _gmap_get_marker_titles();
  }
  cache_set('gmap_marker_titles','cache',serialize($titles));
  return $titles;
}

/**
 * Get the JSON icon data for all the default markers.
 */
function gmap_get_icondata($reset=FALSE){
  static $icons;
  if (is_array($icons) && !$reset) {
    return $icons;
  }

  $icons = cache_get('gmap_icondata');
  if ($icons) {
    $icons = unserialize($icons->data);
  }

  if ($reset || !$icons) {
    require_once(drupal_get_path('module','gmap') .'/gmap_markerinfo.inc');
    $icons = _gmap_get_icondata();
  }
  cache_set('gmap_icondata','cache',serialize($icons));
  return $icons;
}

/**
 * Utility function to allow high-precision decimals to work with the SQL layer.
 * Use as an unquoted %s parameter.
 */
function gmap_decimal($num) {
  // Paraphrased from postgresql documentation:
  //
  // Numbers in SQL can be in one of these forms:
  //   digits
  //   digits.[digits][e[+-]digits]
  //   [digits].digits[e[+-]digits]
  //   digitse[+-]digits
  // where "digits" is one or more decimal digits.

  // Trim extra whitespace
  $num = trim($num);
  // Check if we're in an acceptable form.
  if (preg_match('/^[+\-]?((\d+)|(\d+\.\d*)|(\d*\.\d+))(e[+\-]?\d+)?$/',$num)===1) {
    // Good, we can pass that right along.
    return $num;
  }
  // Otherwise, cast to float, possibly losing precision.
  return (float) $num;
}
